package ooo.reindeer.concurrent.filequeue.log;

import ooo.reindeer.concurrent.filequeue.exception.FileFormatException;
import ooo.reindeer.logging.ILogger;
import ooo.reindeer.logging.LogUtil;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.concurrent.atomic.AtomicInteger;


public class LogIndex {
    private static final ILogger logger = LogUtil.getLogger(LogIndex.class);
    private final int dbFileLimitLength = 40;
    private RandomAccessFile dbRandFile = null;
    private FileChannel fc;
    private MappedByteBuffer mappedByteBuffer;


    private String magicString = null;
    private int version = -1;
    private int readerPosition = -1;
    private int writerPosition = -1;
    private long readerIndex = -1;
    private long writerIndex = -1;
    private AtomicInteger size = new AtomicInteger();

    public LogIndex(String path) throws IOException, FileFormatException {
        File dbFile = new File(path);

        // 文件不存在，创建文件
        if (dbFile.exists() == false) {
            dbFile.createNewFile();
            dbRandFile = new RandomAccessFile(dbFile, "rwd");
            dbRandFile.write(LogEntity.MAGIC.getBytes());// magic
            dbRandFile.writeInt(1);// 8version
            dbRandFile.writeInt(LogEntity.messageStartPosition);// 12 reader
            // pos
            dbRandFile.writeInt(LogEntity.messageStartPosition); // 16write
            // pos
            long fileNum = System.currentTimeMillis();
            dbRandFile.writeLong(fileNum);// 20readerindex
            dbRandFile.writeLong(fileNum);// 28writerindex
            dbRandFile.writeInt(0);// 36 size
            magicString = LogEntity.MAGIC;
            version = 1;
            readerPosition = LogEntity.messageStartPosition;
            writerPosition = LogEntity.messageStartPosition;
            readerIndex = fileNum;
            writerIndex = fileNum;
        } else {
            dbRandFile = new RandomAccessFile(dbFile, "rwd");
            if (dbRandFile.length() < 40) {
                throw new FileFormatException("file format error");
            }
            byte[] b = new byte[this.dbFileLimitLength];
            dbRandFile.read(b);
            ByteBuffer buffer = ByteBuffer.wrap(b);
            b = new byte[LogEntity.MAGIC.getBytes().length];
            buffer.get(b);
            magicString = new String(b);
            version = buffer.getInt();
            readerPosition = buffer.getInt();
            writerPosition = buffer.getInt();
            readerIndex = buffer.getLong();
            writerIndex = buffer.getLong();
            size.set(buffer.getInt());

        }
        fc = dbRandFile.getChannel();
        mappedByteBuffer = fc.map(MapMode.READ_WRITE, 0, this.dbFileLimitLength);
    }


    public void close() {
        try {
            mappedByteBuffer.force();
            AccessController.doPrivileged(new PrivilegedAction<Object>() {
                public Object run() {
                    try {
                        Method getCleanerMethod = mappedByteBuffer.getClass().getMethod("cleaner");
                        getCleanerMethod.setAccessible(true);
                        sun.misc.Cleaner cleaner = (sun.misc.Cleaner) getCleanerMethod.invoke(mappedByteBuffer,
                                new Object[0]);
                        cleaner.clean();
                    } catch (Exception e) {
                        logger.error("close logindexy file error:", e);
                    }
                    return null;
                }
            });
            fc.close();
            dbRandFile.close();
            mappedByteBuffer = null;
            fc = null;
            dbRandFile = null;
        } catch (IOException e) {
            logger.error("close logindex file error:", e);
        }
    }

    public void decrementSize() {
        int num = size.decrementAndGet();
        mappedByteBuffer.position(36);
        mappedByteBuffer.putInt(num);
    }

    public String getMagicString() {
        return magicString;
    }

    public long getReaderIndex() {
        return readerIndex;
    }

    public int getReaderPosition() {
        return readerPosition;
    }

    public int getSize() {
        return size.get();
    }

    public int getVersion() {
        return version;
    }

    public long getWriterIndex() {
        return writerIndex;
    }

    public int getWriterPosition() {
        return writerPosition;
    }

    public String headerInfo() {
        return toString();
    }

    @Override
    public String toString() {
        return "LogIndex{" +
                "magicString='" + magicString + '\'' +
                ", version=" + version +
                ", readerPosition=" + readerPosition +
                ", writerPosition=" + writerPosition +
                ", readerIndex=" + readerIndex +
                ", writerIndex=" + writerIndex +
                ", size=" + size +
                '}';
    }

    public void incrementSize() {
        int num = size.incrementAndGet();
        mappedByteBuffer.position(36);
        mappedByteBuffer.putInt(num);
    }


    public void putReaderIndex(long index) {
        mappedByteBuffer.position(20);
        mappedByteBuffer.putLong(index);
        this.readerIndex = index;
    }


    public void putReaderPosition(int pos) {
        mappedByteBuffer.position(12);
        mappedByteBuffer.putInt(pos);
        this.readerPosition = pos;
    }


    public void putWriterIndex(long index) {
        mappedByteBuffer.position(28);
        mappedByteBuffer.putLong(index);
        this.writerIndex = index;
    }


    public void putWriterPosition(int pos) {
        mappedByteBuffer.position(16);
        mappedByteBuffer.putInt(pos);
        this.writerPosition = pos;
    }

}
